<?php
namespace TYPO3\CMS\IndexedSearch\Utility;

/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

/**
 * TYPO3: Had to change name to "\TYPO3\CMS\IndexedSearch\Utility\DoubleMetaPhoneUtility" from just "DoubleMetaPhone" because TYPO3 requires a user class to be prefixed so:
 * TYPO3: If you want to use this metaphone method instead of the default in the indexer you can enable it in the extension configuration
 * TYPO3: Of course you can write your own metaphone hook methods by taking this class and configuration as example (also see ext_localconf.php)
 */
class DoubleMetaPhoneUtility {

	/**
	 * @var string
	 */
	public $original = '';

	/**
	 * @var string
	 */
	public $primary = '';

	/**
	 * @var string
	 */
	public $secondary = '';

	/**
	 * @var int
	 */
	public $length = 0;

	/**
	 * @var int
	 */
	public $last = 0;

	/**
	 * @var int
	 */
	public $current = 0;

	//  methods
	// TYPO3 specific API to this class. BEGIN
	/**
	 * Metaphone
	 *
	 * @param string $string
	 * @param int $sys_language_uid
	 * @return string
	 */
	public function metaphone($string, $sys_language_uid = 0) {
		$res = $this->DoubleMetaPhone($string);
		return $res['primary'];
	}

	// TYPO3 specific API to this class. END
	// Public method
	/**
	 * Double metaphone
	 *
	 * @param string $string
	 * @return array
	 */
	public function DoubleMetaPhone($string) {
		$this->primary = '';
		$this->secondary = '';
		$this->current = 0;
		$this->current = 0;
		$this->length = strlen($string);
		$this->last = $this->length - 1;
		$this->original = $string . '     ';
		$this->original = strtoupper($this->original);
		// skip this at beginning of word
		if ($this->StringAt($this->original, 0, 2, array('GN', 'KN', 'PN', 'WR', 'PS'))) {
			$this->current++;
		}
		// Initial 'X' is pronounced 'Z' e.g. 'Xavier'
		if ($this->original[0] === 'X') {
			$this->primary .= 'S';
			// 'Z' maps to 'S'
			$this->secondary .= 'S';
			$this->current++;
		}
		// main loop
		while (strlen($this->primary) < 4 || strlen($this->secondary < 4)) {
			if ($this->current >= $this->length) {
				break;
			}
			switch (substr($this->original, $this->current, 1)) {
				case 'A':

				case 'E':

				case 'I':

				case 'O':

				case 'U':

				case 'Y':
					if ($this->current == 0) {
						// all init vowels now map to 'A'
						$this->primary .= 'A';
						$this->secondary .= 'A';
					}
					$this->current += 1;
					break;
				case 'B':
					// '-mb', e.g. "dumb", already skipped over ...
					$this->primary .= 'P';
					$this->secondary .= 'P';
					if (substr($this->original, $this->current + 1, 1) == 'B') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					break;
				case 'Ç':
					$this->primary .= 'S';
					$this->secondary .= 'S';
					$this->current += 1;
					break;
				case 'C':
					// various gremanic
					if ($this->current > 1 && !$this->IsVowel($this->original, ($this->current - 2)) && $this->StringAt($this->original, $this->current - 1, 3, array('ACH')) && (substr($this->original, $this->current + 2, 1) != 'I' && (substr($this->original, $this->current + 2, 1) != 'E' || $this->StringAt($this->original, $this->current - 2, 6, array('BACHER', 'MACHER'))))) {
						$this->primary .= 'K';
						$this->secondary .= 'K';
						$this->current += 2;
						break;
					}
					// special case 'caesar'
					if ($this->current == 0 && $this->StringAt($this->original, $this->current, 6, array('CAESAR'))) {
						$this->primary .= 'S';
						$this->secondary .= 'S';
						$this->current += 2;
						break;
					}
					// italian 'chianti'
					if ($this->StringAt($this->original, $this->current, 4, array('CHIA'))) {
						$this->primary .= 'K';
						$this->secondary .= 'K';
						$this->current += 2;
						break;
					}
					if ($this->StringAt($this->original, $this->current, 2, array('CH'))) {
						// find 'michael'
						if ($this->current > 0 && $this->StringAt($this->original, $this->current, 4, array('CHAE'))) {
							$this->primary .= 'K';
							$this->secondary .= 'X';
							$this->current += 2;
							break;
						}
						// greek roots e.g. 'chemistry', 'chorus'
						if ($this->current == 0 && ($this->StringAt($this->original, $this->current + 1, 5, array('HARAC', 'HARIS')) || $this->StringAt($this->original, $this->current + 1, 3, array('HOR', 'HYM', 'HIA', 'HEM'))) && !$this->StringAt($this->original, 0, 5, array('CHORE'))) {
							$this->primary .= 'K';
							$this->secondary .= 'K';
							$this->current += 2;
							break;
						}
						// germanic, greek, or otherwise 'ch' for 'kh' sound
						if ($this->StringAt($this->original, 0, 4, array('VAN ', 'VON ')) || $this->StringAt($this->original, 0, 3, array('SCH')) || $this->StringAt($this->original, $this->current - 2, 6, array('ORCHES', 'ARCHIT', 'ORCHID')) || $this->StringAt($this->original, $this->current + 2, 1, array('T', 'S')) || ($this->StringAt($this->original, $this->current - 1, 1, array('A', 'O', 'U', 'E')) || $this->current == 0) && $this->StringAt($this->original, $this->current + 2, 1, array('L', 'R', 'N', 'M', 'B', 'H', 'F', 'V', 'W', ' '))) {
							$this->primary .= 'K';
							$this->secondary .= 'K';
						} else {
							if ($this->current > 0) {
								if ($this->StringAt($this->original, 0, 2, array('MC'))) {
									// e.g. 'McHugh'
									$this->primary .= 'K';
									$this->secondary .= 'K';
								} else {
									$this->primary .= 'X';
									$this->secondary .= 'K';
								}
							} else {
								$this->primary .= 'X';
								$this->secondary .= 'X';
							}
						}
						$this->current += 2;
						break;
					}
					// e.g. 'czerny'
					if ($this->StringAt($this->original, $this->current, 2, array('CZ')) && !$this->StringAt($this->original, ($this->current - 2), 4, array('WICZ'))) {
						$this->primary .= 'S';
						$this->secondary .= 'X';
						$this->current += 2;
						break;
					}
					// e.g. 'focaccia'
					if ($this->StringAt($this->original, $this->current + 1, 3, array('CIA'))) {
						$this->primary .= 'X';
						$this->secondary .= 'X';
						$this->current += 3;
						break;
					}
					// double 'C', but not McClellan'
					if ($this->StringAt($this->original, $this->current, 2, array('CC')) && !($this->current == 1 && $this->original[0] === 'M')) {
						// 'bellocchio' but not 'bacchus'
						if ($this->StringAt($this->original, $this->current + 2, 1, array('I', 'E', 'H')) && !$this->StringAt($this->original, ($this->current + 2), 2, array('HU'))) {
							// 'accident', 'accede', 'succeed'
							if ($this->current == 1 && substr($this->original, $this->current - 1, 1) == 'A' || $this->StringAt($this->original, $this->current - 1, 5, array('UCCEE', 'UCCES'))) {
								$this->primary .= 'KS';
								$this->secondary .= 'KS';
							} else {
								$this->primary .= 'X';
								$this->secondary .= 'X';
							}
							$this->current += 3;
							break;
						} else {
							// Pierce's rule
							$this->primary .= 'K';
							$this->secondary .= 'K';
							$this->current += 2;
							break;
						}
					}
					if ($this->StringAt($this->original, $this->current, 2, array('CK', 'CG', 'CQ'))) {
						$this->primary .= 'K';
						$this->secondary .= 'K';
						$this->current += 2;
						break;
					}
					if ($this->StringAt($this->original, $this->current, 2, array('CI', 'CE', 'CY'))) {
						// italian vs. english
						if ($this->StringAt($this->original, $this->current, 3, array('CIO', 'CIE', 'CIA'))) {
							$this->primary .= 'S';
							$this->secondary .= 'X';
						} else {
							$this->primary .= 'S';
							$this->secondary .= 'S';
						}
						$this->current += 2;
						break;
					}
					// else
					$this->primary .= 'K';
					$this->secondary .= 'K';
					// name sent in 'mac caffrey', 'mac gregor'
					if ($this->StringAt($this->original, $this->current + 1, 2, array(' C', ' Q', ' G'))) {
						$this->current += 3;
					} else {
						if ($this->StringAt($this->original, $this->current + 1, 1, array('C', 'K', 'Q')) && !$this->StringAt($this->original, ($this->current + 1), 2, array('CE', 'CI'))) {
							$this->current += 2;
						} else {
							$this->current += 1;
						}
					}
					break;
				case 'D':
					if ($this->StringAt($this->original, $this->current, 2, array('DG'))) {
						if ($this->StringAt($this->original, $this->current + 2, 1, array('I', 'E', 'Y'))) {
							// e.g. 'edge'
							$this->primary .= 'J';
							$this->secondary .= 'J';
							$this->current += 3;
							break;
						} else {
							// e.g. 'edgar'
							$this->primary .= 'TK';
							$this->secondary .= 'TK';
							$this->current += 2;
							break;
						}
					}
					if ($this->StringAt($this->original, $this->current, 2, array('DT', 'DD'))) {
						$this->primary .= 'T';
						$this->secondary .= 'T';
						$this->current += 2;
						break;
					}
					// else
					$this->primary .= 'T';
					$this->secondary .= 'T';
					$this->current += 1;
					break;
				case 'F':
					if (substr($this->original, $this->current + 1, 1) == 'F') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'F';
					$this->secondary .= 'F';
					break;
				case 'G':
					if (substr($this->original, $this->current + 1, 1) == 'H') {
						if ($this->current > 0 && !$this->IsVowel($this->original, ($this->current - 1))) {
							$this->primary .= 'K';
							$this->secondary .= 'K';
							$this->current += 2;
							break;
						}
						if ($this->current < 3) {
							// 'ghislane', 'ghiradelli'
							if ($this->current == 0) {
								if (substr($this->original, $this->current + 2, 1) == 'I') {
									$this->primary .= 'J';
									$this->secondary .= 'J';
								} else {
									$this->primary .= 'K';
									$this->secondary .= 'K';
								}
								$this->current += 2;
								break;
							}
						}
						// Parker's rule (with some further refinements) - e.g. 'hugh'
						if ($this->current > 1 && $this->StringAt($this->original, $this->current - 2, 1, array('B', 'H', 'D')) || $this->current > 2 && $this->StringAt($this->original, $this->current - 3, 1, array('B', 'H', 'D')) || $this->current > 3 && $this->StringAt($this->original, $this->current - 4, 1, array('B', 'H'))) {
							$this->current += 2;
							break;
						} else {
							// e.g. 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough'
							if ($this->current > 2 && substr($this->original, $this->current - 1, 1) == 'U' && $this->StringAt($this->original, $this->current - 3, 1, array('C', 'G', 'L', 'R', 'T'))) {
								$this->primary .= 'F';
								$this->secondary .= 'F';
							} elseif ($this->current > 0 && substr($this->original, $this->current - 1, 1) != 'I') {
								$this->primary .= 'K';
								$this->secondary .= 'K';
							}
							$this->current += 2;
							break;
						}
					}
					if (substr($this->original, $this->current + 1, 1) == 'N') {
						if ($this->current == 1 && $this->IsVowel($this->original, 0) && !$this->SlavoGermanic($this->original)) {
							$this->primary .= 'KN';
							$this->secondary .= 'N';
						} else {
							// not e.g. 'cagney'
							if (!$this->StringAt($this->original, ($this->current + 2), 2, array('EY')) && substr($this->original, $this->current + 1) != 'Y' && !$this->SlavoGermanic($this->original)) {
								$this->primary .= 'N';
								$this->secondary .= 'KN';
							} else {
								$this->primary .= 'KN';
								$this->secondary .= 'KN';
							}
						}
						$this->current += 2;
						break;
					}
					// 'tagliaro'
					if ($this->StringAt($this->original, $this->current + 1, 2, array('LI')) && !$this->SlavoGermanic($this->original)) {
						$this->primary .= 'KL';
						$this->secondary .= 'L';
						$this->current += 2;
						break;
					}
					// -ges-, -gep-, -gel- at beginning
					if ($this->current == 0 && (substr($this->original, $this->current + 1, 1) == 'Y' || $this->StringAt($this->original, $this->current + 1, 2, array(
						'ES',
						'EP',
						'EB',
						'EL',
						'EY',
						'IB',
						'IL',
						'IN',
						'IE',
						'EI',
						'ER'
					)))) {
						$this->primary .= 'K';
						$this->secondary .= 'J';
						$this->current += 2;
						break;
					}
					// -ger-, -gy-
					if (($this->StringAt($this->original, $this->current + 1, 2, array('ER')) || substr($this->original, $this->current + 1, 1) == 'Y') && !$this->StringAt($this->original, 0, 6, array('DANGER', 'RANGER', 'MANGER')) && !$this->StringAt($this->original, ($this->current - 1), 1, array('E', 'I')) && !$this->StringAt($this->original, ($this->current - 1), 3, array('RGY', 'OGY'))) {
						$this->primary .= 'K';
						$this->secondary .= 'J';
						$this->current += 2;
						break;
					}
					// italian e.g. 'biaggi'
					if ($this->StringAt($this->original, $this->current + 1, 1, array('E', 'I', 'Y')) || $this->StringAt($this->original, $this->current - 1, 4, array('AGGI', 'OGGI'))) {
						// obvious germanic
						if ($this->StringAt($this->original, 0, 4, array('VAN ', 'VON ')) || $this->StringAt($this->original, 0, 3, array('SCH')) || $this->StringAt($this->original, $this->current + 1, 2, array('ET'))) {
							$this->primary .= 'K';
							$this->secondary .= 'K';
						} else {
							// always soft if french ending
							if ($this->StringAt($this->original, $this->current + 1, 4, array('IER '))) {
								$this->primary .= 'J';
								$this->secondary .= 'J';
							} else {
								$this->primary .= 'J';
								$this->secondary .= 'K';
							}
						}
						$this->current += 2;
						break;
					}
					if (substr($this->original, $this->current + 1, 1) == 'G') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'K';
					$this->secondary .= 'K';
					break;
				case 'H':
					// only keep if first & before vowel or btw. 2 vowels
					if (($this->current == 0 || $this->IsVowel($this->original, $this->current - 1)) && $this->IsVowel($this->original, $this->current + 1)) {
						$this->primary .= 'H';
						$this->secondary .= 'H';
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					break;
				case 'J':
					// obvious spanish, 'jose', 'san jacinto'
					if ($this->StringAt($this->original, $this->current, 4, array('JOSE')) || $this->StringAt($this->original, 0, 4, array('SAN '))) {
						if ($this->current == 0 && substr($this->original, $this->current + 4, 1) == ' ' || $this->StringAt($this->original, 0, 4, array('SAN '))) {
							$this->primary .= 'H';
							$this->secondary .= 'H';
						} else {
							$this->primary .= 'J';
							$this->secondary .= 'H';
						}
						$this->current += 1;
						break;
					}
					if ($this->current == 0 && !$this->StringAt($this->original, $this->current, 4, array('JOSE'))) {
						$this->primary .= 'J';
						// Yankelovich/Jankelowicz
						$this->secondary .= 'A';
					} else {
						// spanish pron. of .e.g. 'bajador'
						if ($this->IsVowel($this->original, $this->current - 1) && !$this->SlavoGermanic($this->original) && (substr($this->original, $this->current + 1, 1) == 'A' || substr($this->original, $this->current + 1, 1) == 'O')) {
							$this->primary .= 'J';
							$this->secondary .= 'H';
						} else {
							if ($this->current == $this->last) {
								$this->primary .= 'J';
								$this->secondary .= '';
							} else {
								if (!$this->StringAt($this->original, ($this->current + 1), 1, array('L', 'T', 'K', 'S', 'N', 'M', 'B', 'Z')) && !$this->StringAt($this->original, ($this->current - 1), 1, array('S', 'K', 'L'))) {
									$this->primary .= 'J';
									$this->secondary .= 'J';
								}
							}
						}
					}
					if (substr($this->original, $this->current + 1, 1) == 'J') {
						// it could happen
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					break;
				case 'K':
					if (substr($this->original, $this->current + 1, 1) == 'K') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'K';
					$this->secondary .= 'K';
					break;
				case 'L':
					if (substr($this->original, $this->current + 1, 1) == 'L') {
						// spanish e.g. 'cabrillo', 'gallegos'
						if ($this->current == $this->length - 3 && $this->StringAt($this->original, $this->current - 1, 4, array('ILLO', 'ILLA', 'ALLE')) || ($this->StringAt($this->original, $this->last - 1, 2, array('AS', 'OS')) || $this->StringAt($this->original, $this->last, 1, array('A', 'O'))) && $this->StringAt($this->original, $this->current - 1, 4, array('ALLE'))) {
							$this->primary .= 'L';
							$this->secondary .= '';
							$this->current += 2;
							break;
						}
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'L';
					$this->secondary .= 'L';
					break;
				case 'M':
					if ($this->StringAt($this->original, $this->current - 1, 3, array('UMB')) && ($this->current + 1 == $this->last || $this->StringAt($this->original, $this->current + 2, 2, array('ER'))) || substr($this->original, $this->current + 1, 1) == 'M') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'M';
					$this->secondary .= 'M';
					break;
				case 'N':
					if (substr($this->original, $this->current + 1, 1) == 'N') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'N';
					$this->secondary .= 'N';
					break;
				case 'Ñ':
					$this->current += 1;
					$this->primary .= 'N';
					$this->secondary .= 'N';
					break;
				case 'P':
					if (substr($this->original, $this->current + 1, 1) == 'H') {
						$this->current += 2;
						$this->primary .= 'F';
						$this->secondary .= 'F';
						break;
					}
					// also account for "campbell" and "raspberry"
					if ($this->StringAt($this->original, $this->current + 1, 1, array('P', 'B'))) {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'P';
					$this->secondary .= 'P';
					break;
				case 'Q':
					if (substr($this->original, $this->current + 1, 1) == 'Q') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'K';
					$this->secondary .= 'K';
					break;
				case 'R':
					// french e.g. 'rogier', but exclude 'hochmeier'
					if ($this->current == $this->last && !$this->SlavoGermanic($this->original) && $this->StringAt($this->original, $this->current - 2, 2, array('IE')) && !$this->StringAt($this->original, ($this->current - 4), 2, array('ME', 'MA'))) {
						$this->primary .= '';
						$this->secondary .= 'R';
					} else {
						$this->primary .= 'R';
						$this->secondary .= 'R';
					}
					if (substr($this->original, $this->current + 1, 1) == 'R') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					break;
				case 'S':
					// special cases 'island', 'isle', 'carlisle', 'carlysle'
					if ($this->StringAt($this->original, $this->current - 1, 3, array('ISL', 'YSL'))) {
						$this->current += 1;
						break;
					}
					// special case 'sugar-'
					if ($this->current == 0 && $this->StringAt($this->original, $this->current, 5, array('SUGAR'))) {
						$this->primary .= 'X';
						$this->secondary .= 'S';
						$this->current += 1;
						break;
					}
					if ($this->StringAt($this->original, $this->current, 2, array('SH'))) {
						// germanic
						if ($this->StringAt($this->original, $this->current + 1, 4, array('HEIM', 'HOEK', 'HOLM', 'HOLZ'))) {
							$this->primary .= 'S';
							$this->secondary .= 'S';
						} else {
							$this->primary .= 'X';
							$this->secondary .= 'X';
						}
						$this->current += 2;
						break;
					}
					// italian & armenian
					if ($this->StringAt($this->original, $this->current, 3, array('SIO', 'SIA')) || $this->StringAt($this->original, $this->current, 4, array('SIAN'))) {
						if (!$this->SlavoGermanic($this->original)) {
							$this->primary .= 'S';
							$this->secondary .= 'X';
						} else {
							$this->primary .= 'S';
							$this->secondary .= 'S';
						}
						$this->current += 3;
						break;
					}
					// german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider'
					// also, -sz- in slavic language altho in hungarian it is pronounced 's'
					if ($this->current == 0 && $this->StringAt($this->original, $this->current + 1, 1, array('M', 'N', 'L', 'W')) || $this->StringAt($this->original, $this->current + 1, 1, array('Z'))) {
						$this->primary .= 'S';
						$this->secondary .= 'X';
						if ($this->StringAt($this->original, $this->current + 1, 1, array('Z'))) {
							$this->current += 2;
						} else {
							$this->current += 1;
						}
						break;
					}
					if ($this->StringAt($this->original, $this->current, 2, array('SC'))) {
						// Schlesinger's rule
						if (substr($this->original, $this->current + 2, 1) == 'H') {
							// dutch origin, e.g. 'school', 'schooner'
							if ($this->StringAt($this->original, $this->current + 3, 2, array('OO', 'ER', 'EN', 'UY', 'ED', 'EM'))) {
								// 'schermerhorn', 'schenker'
								if ($this->StringAt($this->original, $this->current + 3, 2, array('ER', 'EN'))) {
									$this->primary .= 'X';
									$this->secondary .= 'SK';
								} else {
									$this->primary .= 'SK';
									$this->secondary .= 'SK';
								}
								$this->current += 3;
								break;
							} else {
								if ($this->current == 0 && !$this->IsVowel($this->original, 3) && substr($this->original, $this->current + 3, 1) != 'W') {
									$this->primary .= 'X';
									$this->secondary .= 'S';
								} else {
									$this->primary .= 'X';
									$this->secondary .= 'X';
								}
								$this->current += 3;
								break;
							}
						}
						if ($this->StringAt($this->original, $this->current + 2, 1, array('I', 'E', 'Y'))) {
							$this->primary .= 'S';
							$this->secondary .= 'S';
							$this->current += 3;
							break;
						}
						// else
						$this->primary .= 'SK';
						$this->secondary .= 'SK';
						$this->current += 3;
						break;
					}
					// french e.g. 'resnais', 'artois'
					if ($this->current == $this->last && $this->StringAt($this->original, $this->current - 2, 2, array('AI', 'OI'))) {
						$this->primary .= '';
						$this->secondary .= 'S';
					} else {
						$this->primary .= 'S';
						$this->secondary .= 'S';
					}
					if ($this->StringAt($this->original, $this->current + 1, 1, array('S', 'Z'))) {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					break;
				case 'T':
					if ($this->StringAt($this->original, $this->current, 4, array('TION'))) {
						$this->primary .= 'X';
						$this->secondary .= 'X';
						$this->current += 3;
						break;
					}
					if ($this->StringAt($this->original, $this->current, 3, array('TIA', 'TCH'))) {
						$this->primary .= 'X';
						$this->secondary .= 'X';
						$this->current += 3;
						break;
					}
					if ($this->StringAt($this->original, $this->current, 2, array('TH')) || $this->StringAt($this->original, $this->current, 3, array('TTH'))) {
						// special case 'thomas', 'thames' or germanic
						if ($this->StringAt($this->original, $this->current + 2, 2, array('OM', 'AM')) || $this->StringAt($this->original, 0, 4, array('VAN ', 'VON ')) || $this->StringAt($this->original, 0, 3, array('SCH'))) {
							$this->primary .= 'T';
							$this->secondary .= 'T';
						} else {
							$this->primary .= '0';
							$this->secondary .= 'T';
						}
						$this->current += 2;
						break;
					}
					if ($this->StringAt($this->original, $this->current + 1, 1, array('T', 'D'))) {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'T';
					$this->secondary .= 'T';
					break;
				case 'V':
					if (substr($this->original, $this->current + 1, 1) == 'V') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					$this->primary .= 'F';
					$this->secondary .= 'F';
					break;
				case 'W':
					// can also be in middle of word
					if ($this->StringAt($this->original, $this->current, 2, array('WR'))) {
						$this->primary .= 'R';
						$this->secondary .= 'R';
						$this->current += 2;
						break;
					}
					if ($this->current == 0 && ($this->IsVowel($this->original, $this->current + 1) || $this->StringAt($this->original, $this->current, 2, array('WH')))) {
						// Wasserman should match Vasserman
						if ($this->IsVowel($this->original, $this->current + 1)) {
							$this->primary .= 'A';
							$this->secondary .= 'F';
						} else {
							// need Uomo to match Womo
							$this->primary .= 'A';
							$this->secondary .= 'A';
						}
					}
					// Arnow should match Arnoff
					if ($this->current == $this->last && $this->IsVowel($this->original, $this->current - 1) || $this->StringAt($this->original, $this->current - 1, 5, array('EWSKI', 'EWSKY', 'OWSKI', 'OWSKY')) || $this->StringAt($this->original, 0, 3, array('SCH'))) {
						$this->primary .= '';
						$this->secondary .= 'F';
						$this->current += 1;
						break;
					}
					// polish e.g. 'filipowicz'
					if ($this->StringAt($this->original, $this->current, 4, array('WICZ', 'WITZ'))) {
						$this->primary .= 'TS';
						$this->secondary .= 'FX';
						$this->current += 4;
						break;
					}
					// else skip it
					$this->current += 1;
					break;
				case 'X':
					// french e.g. breaux
					if (!($this->current == $this->last && ($this->StringAt($this->original, $this->current - 3, 3, array('IAU', 'EAU')) || $this->StringAt($this->original, $this->current - 2, 2, array('AU', 'OU'))))) {
						$this->primary .= 'KS';
						$this->secondary .= 'KS';
					}
					if ($this->StringAt($this->original, $this->current + 1, 1, array('C', 'X'))) {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					break;
				case 'Z':
					// chinese pinyin e.g. 'zhao'
					if (substr($this->original, $this->current + 1, 1) == 'H') {
						$this->primary .= 'J';
						$this->secondary .= 'J';
						$this->current += 2;
						break;
					} elseif ($this->StringAt($this->original, $this->current + 1, 2, array('ZO', 'ZI', 'ZA')) || $this->SlavoGermanic($this->original) && ($this->current > 0 && substr($this->original, $this->current - 1, 1) != 'T')) {
						$this->primary .= 'S';
						$this->secondary .= 'TS';
					} else {
						$this->primary .= 'S';
						$this->secondary .= 'S';
					}
					if (substr($this->original, $this->current + 1, 1) == 'Z') {
						$this->current += 2;
					} else {
						$this->current += 1;
					}
					break;
				default:
					$this->current += 1;
			}
		}
		// end while
		$this->primary = substr($this->primary, 0, 4);
		$this->secondary = substr($this->secondary, 0, 4);
		$result['primary'] = $this->primary;
		$result['secondary'] = $this->secondary;
		return $result;
	}

	// end of function MetaPhone
	// Private methods
	/**
	 * String at
	 *
	 * @param string $string
	 * @param int $start
	 * @param int $length
	 * @param array $list
	 * @return bool
	 */
	public function StringAt($string, $start, $length, $list) {
		if ($start < 0 || $start >= strlen($string)) {
			return 0;
		}
		$listCount = count($list);
		for ($i = 0; $i < $listCount; $i++) {
			if ($list[$i] == substr($string, $start, $length)) {
				return 1;
			}
		}
		return 0;
	}

	/**
	 * Is vowel?
	 *
	 * @param string $string
	 * @param int $pos
	 * @return bool|int
	 */
	public function IsVowel($string, $pos) {
		return preg_match('/[AEIOUY]/', substr($string, $pos, 1));
	}

	/**
	 * Is slavogermanic?
	 *
	 * @param string $string
	 * @return bool|int
	 */
	public function SlavoGermanic($string) {
		return preg_match('/W|K|CZ|WITZ/', $string);
	}

}
